package downstream

import (
	"context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/status"
	"mall/api/inventory"
	"mall/api/mall"
	"mall/internal/conf"
	"mall/internal/constant"
	"mall/internal/model"
	"mall/pkg/ecode"
	"mall/pkg/log"
	"mall/pkg/network"
	"mall/pkg/request_key"
	"strconv"
	"strings"
	"time"
)

func newInventoryClient(ctx context.Context, addr string) (
	client inventory.InventoryClient, err error) {
	var conn *grpc.ClientConn
	conn, err = grpc.Dial(addr, grpc.WithInsecure(),
		grpc.WithUnaryInterceptor(network.ClientInterceptor(
			"bilin_inventory", time.Second*5, inventoryErrorTranslator)))
	if err != nil {
		err = ecode.AppendMessage(constant.ErrRemoteError, "仓库服务地址无法建立有效连接, err=%s", err.Error())
		log.FromContext(ctx).Warnw(err.Error())
		return
	}
	client = inventory.NewInventoryClient(conn)
	return
}

func inventoryErrorTranslator(err error) (errCode *ecode.ErrorCode) {
	rpcStatus := status.Convert(err)
	for _, detail := range rpcStatus.Details() {
		switch t := detail.(type) {
		case *inventory.DefaultResp:
			errCode = ecode.NewError(t.ErrorCode, t.ErrorMessage, ecode.ErrorType{
				GRPCCode: rpcStatus.Code(),
				ErrType:  t.ErrorType,
			})
			return
		}
	}
	errCode = ecode.AppendMessage(
		constant.ErrRemoteError, "仓库服务系统错误，请稍后再试")
	return
}

func (d *Downstream) CalculateDeliverFee(ctx context.Context, req *inventory.CalculateDeliverFeeRequest) (
	warehouseMap map[uint32]string, err error) {
	if d.inventoryClient == nil {
		if d.inventoryClient, err = newInventoryClient(
			ctx, conf.Conf.Downstream.InventoryAddress); err != nil {
			return
		}
	}
	var resp *inventory.GetSelectionWarehouseResponse
	clientCtx, cancel := request_key.NewOutGoingContext(ctx, time.Second*3)
	defer cancel()
	if resp, err = d.inventoryClient.CalculateDeliverFee(clientCtx, req); err != nil {
		return
	}
	warehouseMap = make(map[uint32]string)
	for _, warehouse := range resp.Warehouses {
		warehouseMap[warehouse.Id] = warehouse.Name
	}
	return
}

func (d *Downstream) CreateFreezeOrder(ctx context.Context, sgId uint32, requestId string,
	details []*inventory.CreateFreezeOrderDetail, orderKey string) (freezeOrderNumber string, err error) {
	var resp *inventory.CreateOrderReply
	if d.inventoryClient == nil {
		if d.inventoryClient, err = newInventoryClient(
			ctx, conf.Conf.Downstream.InventoryAddress); err != nil {
			return
		}
	}
	clientCtx, cancel := request_key.NewOutGoingContext(ctx, time.Second*3)
	defer cancel()
	if resp, err = d.inventoryClient.CreateFreezeOrder(clientCtx, &inventory.CreateFreezeOrderRequest{
		SgId:       sgId,
		RequestNum: requestId,
		Source:     inventory.SourceEnum_MALL_ORDER,
		Details:    details,
		OrderKey:   orderKey,
	}); err != nil {
		return
	}
	freezeOrderNumber = resp.OrderNumber
	return
}

func (d *Downstream) EndFreezeOrder(ctx context.Context, requestId string) (err error) {
	if d.inventoryClient == nil {
		if d.inventoryClient, err = newInventoryClient(
			ctx, conf.Conf.Downstream.InventoryAddress); err != nil {
			return
		}
	}
	clientCtx, cancel := request_key.NewOutGoingContext(ctx, time.Second*3)
	defer cancel()
	if _, err = d.inventoryClient.EndFreezeOrder(clientCtx, &inventory.EndFreezeOrderRequest{
		RequestNum: requestId,
		Source:     inventory.SourceEnum_MALL_ORDER,
	}); err != nil {
		return
	}
	return
}

func (d *Downstream) GetWarehouseInfo(ctx context.Context, sgId, pStoreId, Id uint32) (
	resp *inventory.GetWarehouseAddressInfoReply, err error) {
	if d.inventoryClient == nil {
		if d.inventoryClient, err = newInventoryClient(
			ctx, conf.Conf.Downstream.InventoryAddress); err != nil {
			return
		}
	}
	clientCtx, cancel := request_key.NewOutGoingContext(ctx, time.Second*3)
	defer cancel()
	if resp, err = d.inventoryClient.GetWarehouseAddressInfo(clientCtx, &inventory.GetWarehouseAddressInfoRequest{
		SgId:     sgId,
		PStoreId: pStoreId,
		Id:       Id,
	}); err != nil {
		return
	}
	return
}

func (d *Downstream) GetOrderListStorageStatus(ctx context.Context, sgId uint32, processOrderNumbers []string) (
	statusMap map[string]uint32, err error) {
	if d.inventoryClient == nil {
		if d.inventoryClient, err = newInventoryClient(
			ctx, conf.Conf.Downstream.InventoryAddress); err != nil {
			return
		}
	}
	var resp *inventory.GetOrderListStorageResponse
	clientCtx, cancel := request_key.NewOutGoingContext(ctx, time.Second*3)
	defer cancel()
	if resp, err = d.inventoryClient.GetOrderListStorage(
		clientCtx, &inventory.GetOrderListStorageRequest{
			SgId:      sgId,
			OrderList: processOrderNumbers,
		}); err != nil {
		return
	}
	statusMap = make(map[string]uint32)
	for _, order := range resp.OrderList {
		statusMap[order.OrderNumber] = order.StorageStatus
	}
	return
}

func (d *Downstream) GetOrderDetailStorageStatus(ctx context.Context, sgId uint32, processOrderNumber string) (
	storageMap map[uint64]uint32, err error) {
	var resp *inventory.GetOrderInfoStorageResponse
	storageMap = make(map[uint64]uint32)
	if processOrderNumber == "" {
		return
	}
	if d.inventoryClient == nil {
		if d.inventoryClient, err = newInventoryClient(
			ctx, conf.Conf.Downstream.InventoryAddress); err != nil {
			return
		}
	}
	clientCtx, cancel := request_key.NewOutGoingContext(ctx, time.Second*3)
	defer cancel()
	if resp, err = d.inventoryClient.GetOrderInfoStorage(
		clientCtx, &inventory.GetOrderInfoStorageRequest{
			SgId:        sgId,
			OrderNumber: processOrderNumber,
		}); err != nil {
		return
	}
	for _, detail := range resp.Details {
		var skuId int
		if skuId, err = strconv.Atoi(strings.Split(detail.StorageKey, "-")[1]); err != nil {
			err = ecode.WithStack(constant.ErrSystemError)
			return
		}
		storageMap[uint64(skuId)] = detail.StorageStatus
	}
	return
}

func (d *Downstream) CancelProcessOrder(ctx context.Context, orderNumber, reason string) (err error) {
	if d.inventoryClient == nil {
		if d.inventoryClient, err = newInventoryClient(
			ctx, conf.Conf.Downstream.InventoryAddress); err != nil {
			return
		}
	}
	clientCtx, cancel := request_key.NewOutGoingContext(ctx, time.Second*5)
	defer cancel()
	if _, err = d.inventoryClient.CancelProcessOrder(clientCtx, &inventory.CancelProcessOrderRequest{
		Source:         inventory.SourceEnum_MALL_ORDER,
		ExtOrderNumber: orderNumber,
		Reason:         reason,
	}); err != nil {
		return
	}
	return
}

func (d *Downstream) CreateRefundProcessOrder(ctx context.Context, apply *model.Refund,
	req *mall.CustomerRefundDeliverySubmitRequest, refundRemark *model.RefundApplyRemark,
	operationDetails []*inventory.OperationDetail) (inventoryOrderNumber string, err error) {
	if d.inventoryClient == nil {
		if d.inventoryClient, err = newInventoryClient(
			ctx, conf.Conf.Downstream.InventoryAddress); err != nil {
			return
		}
	}
	orderKey := strconv.Itoa(int(apply.MemberId))
	var resp *inventory.CreateOrderReply
	clientCtx, cancel := request_key.NewOutGoingContext(ctx, time.Second*3)
	defer cancel()
	if resp, err = d.inventoryClient.CreateRefundOrder(clientCtx, &inventory.CreateRefundOrderRequest{
		Id:                  apply.WarehouseId,
		ExtOrderNumber:      apply.ApplyNumber,
		Source:              inventory.SourceEnum_MALL_ORDER,
		ProdTrans:           apply.Trans,
		DeliveryType:        inventory.DeliverType(apply.DeliverType),
		PlatformId:          req.PlatformId,
		PlatformOrderNumber: req.PlatformNo,
		MemberId:            orderKey,
		Remark: &inventory.Remark{
			Memo:   refundRemark.Memo,
			Reason: refundRemark.Reason,
		},
		OperationDetails: operationDetails,
		OrderKey:         orderKey,
	}); err != nil {
		return
	}
	inventoryOrderNumber = resp.OrderNumber
	return
}

func (d *Downstream) CreateProcessOrders(ctx context.Context, req *inventory.CreateCombinedProcessOrderRequest) (
	resp *inventory.BatchCreateOrderReply, err error) {
	if d.inventoryClient == nil {
		if d.inventoryClient, err = newInventoryClient(
			ctx, conf.Conf.Downstream.InventoryAddress); err != nil {
			return
		}
	}
	clientCtx, cancel := request_key.NewOutGoingContext(ctx, time.Second*3)
	defer cancel()
	if resp, err = d.inventoryClient.CreateCombinedProcessOrder(clientCtx, req); err != nil {
		return
	}
	return
}
